﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Resources;
using System.Xml;
using Lib.Helper;

namespace MSIACCSL2.Libs
{
    public class DynamicContentLoader : INavigationContentLoader
    {
        private const string _extern = "/EXT:";
        private const char _separator = '|';
        private PageResourceContentLoader _loader = new PageResourceContentLoader();

        // Maps URIs to types (e.g., "/EXT:ExternalPages.dll/Page2.xaml" -> typeof(Page2))
        // Used to determine whether a page has been requested before and to instantiate it
        // quickly if it has
        private static Dictionary<string, Type> _pages = new Dictionary<string, Type>();

        // Maps downloaded DLLs to assembly info (e.g., "ExternalPages.dll" ->
        // "ExternalPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
        // Used to determine whether an assembly has been downloaded before and to
        // store info needed to get information about types in that assembly
        private static Dictionary<string, string> _assemblies = new Dictionary<string, string>();

        public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri,
            AsyncCallback userCallback, object asyncState)
        {
            if (!targetUri.ToString().Contains(_extern))
            {
               
                // If the URI doesn't start with "EXT:," let the default loader handle it
                return _loader.BeginLoad(targetUri, currentUri, userCallback, asyncState);
            }
            else // Otherwise handle it here
            {
                NavigationAsyncResult ar = new NavigationAsyncResult(userCallback, asyncState);
                string fullUri = "/" + targetUri.ToString().Replace("/Views/", "").Replace("/","|");

                // If this page has been loaded before, instantiate it
                // using type information generated the first time and
                // cached in the _pages dictionary
                Type type;

                if (_pages.TryGetValue(fullUri.ToLower(), out type))
                {
                    ar.Result = Activator.CreateInstance(type);
                    
                    ar.CompleteCall(true);
                    return ar;
                }

                // Extract the page URI (e.g., "Page2.xaml")
                int index = fullUri.IndexOf(_separator) + 1;
                string pageUri = fullUri.Substring(index, fullUri.Length - index);

                if ((index = pageUri.IndexOf('?')) > 0)
                    pageUri = pageUri.Substring(0, index); // Strip off query string

                // Extract the assembly URI (e.g., "ExternalPages.dll")
                int len = _extern.Length;
                string assemblyUri = fullUri.Substring(len, fullUri.Length - len);
                assemblyUri = assemblyUri.Substring(0, assemblyUri.IndexOf(_separator));

                // If the assembly has been downloaded before, instantiate
                // the page without downloading the assembly again
                string fullName;

                if (_assemblies.TryGetValue(assemblyUri.ToLower(), out fullName))
                {
                    // Instantiate the page
                    type = GetXamlPageType(pageUri, fullName);
                    ar.Result = Activator.CreateInstance(type);
                    _pages.Add(fullUri.ToLower(), type);
                    ar.CompleteCall(true);
                    return ar;
                }

                // Prepare the IAsyncResult
                ar.TargetUri = fullUri;
                ar.PageUri = pageUri;
                ar.AssemblyUri = assemblyUri;

                // Begin downloading the assembly
                WebClient wc = new WebClient();
                wc.OpenReadCompleted += new OpenReadCompletedEventHandler(OnOpenReadCompleted);
                wc.OpenReadAsync(new Uri(GlobalVar.ReportUrl.Replace("ReportPage","ClientBin") + assemblyUri, UriKind.RelativeOrAbsolute), ar);
                return ar;
            }
        }

        void OnOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                NavigationAsyncResult ar = e.UserState as NavigationAsyncResult;

                // Load the downloaded assembly into the appdomain
                AssemblyPart part = new AssemblyPart();
                Assembly assembly = part.Load(e.Result);
                string fullName = assembly.FullName;

                // Instantiate the page
                Type type = GetXamlPageType(ar.PageUri, fullName);
                ar.Result = Activator.CreateInstance(type);

                // Update the dictionaries
                _pages.Add(ar.TargetUri.ToLower(), type);
                _assemblies.Add(ar.AssemblyUri.ToLower(), fullName);

                // Signal that loading is finished
                ar.CompleteCall(false);
            }
            else
                throw e.Error;
        }

        public bool CanLoad(Uri targetUri, Uri currentUri)
        {
            if (targetUri.ToString().StartsWith(_extern))
                return true;
            else
                return _loader.CanLoad(targetUri, currentUri);
        }

        public void CancelLoad(IAsyncResult asyncResult)
        {
            // Do nothing
        }

        public LoadResult EndLoad(IAsyncResult asyncResult)
        {
            if (asyncResult is NavigationAsyncResult)
                return new LoadResult((asyncResult as NavigationAsyncResult).Result);
            else
                return _loader.EndLoad(asyncResult);
        }

        /////////////////////////////////////////////////////////////////
        // Given the URI of a XAML page (e.g., Page2.xaml) and the name
        // of the assembly that hosts the page, GetXamlPageType extracts
        // the page from the assembly, finds the x:Class attribute, and
        // returns the corresponding type.
        //

        private Type GetXamlPageType(string pageUri, string assemblyFullName)
        {
            string shortName = assemblyFullName.Substring(0, assemblyFullName.IndexOf(','));
            string path = shortName + ";component/" + pageUri;
            StreamResourceInfo sri = Application.GetResourceStream(new Uri( path,
                UriKind.Relative));

            using (XmlReader reader = XmlReader.Create(sri.Stream))
            {
                reader.Read();
                string name = reader.GetAttribute("x:Class");
                return Type.GetType(name + ", " + assemblyFullName, false, true);
            }
        }
    }


}
